﻿//Copyright 2011 - Jonathan Allen

using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections;
using System.Diagnostics.Contracts;
using System.Diagnostics.CodeAnalysis;

namespace Granite.Collections
{

	/// <summary>
	/// This represents a collection of weak references. 
	/// </summary>
	/// <typeparam name="T">The type of elements in the collection.</typeparam>
	public class WeakReferenceCollection<T> : ICollection<T>, IReadOnlyCollection<T> where T : class
	{
		List<WeakReference> m_Collection = new List<WeakReference>();

		/// <summary>
		/// Adds a weak reference to the indicated item
		/// </summary>
		/// <param name="item"></param>
		public void Add(T item)
		{
			if (item == null)
				throw new ArgumentNullException("item");
			Contract.EndContractBlock();

			m_Collection.Add(new WeakReference(item));
		}

		/// <summary>
		/// Adds a list of values to this collection
		/// </summary>
		/// <param name="list"></param>
		public void AddRange(IEnumerable<T> list)
		{
			if (list == null)
				throw new ArgumentNullException("list", "list is null.");
			if (list.Any(i => i == null))
				throw new ArgumentException("list is not allowed to contain null items", "list");
			Contract.EndContractBlock();

			foreach (var item in list)
			{
				Contract.Assume(item != null);
				Add(item);
			}
		}

		/// <summary>
		/// Removes all items from the collection
		/// </summary>
		public void Clear()
		{
			m_Collection.Clear();
			Contract.Assume(m_Collection.Count == Count);
		}

		/// <summary>
		/// Returns true if the item is found in the collection
		/// </summary>
		/// <param name="item"></param>
		/// <returns></returns>
		[Pure]
		public bool Contains(T item)
		{
			if (Count == 0)
				return false;

			return m_Collection.Any(x => object.Equals(x.Target, item));
		}

		/// <summary>
		/// Copies a snapshot of the collection to an array. 
		/// </summary>
		/// <param name="array"></param>
		/// <param name="arrayIndex"></param>
		[Pure]
		public void CopyTo(T[] array, int arrayIndex)
		{
			if (array == null)
				throw new ArgumentNullException("array", "array is null");
			if (arrayIndex < 0)
				throw new ArgumentOutOfRangeException("arrayIndex", "arrayIndex cannot be less than zero");
			if (arrayIndex > array.Length)
				throw new ArgumentOutOfRangeException("arrayIndex", "arrayIndex is greater than the array's length");
			if (Count + arrayIndex > array.Length)
				throw new ArgumentOutOfRangeException("arrayIndex", "Count + arrayIndex is greater than the array's length");
			Contract.EndContractBlock();

			var temp = m_Collection.Select(x => (T)x.Target).Where(x => x != null).ToList();
			for (var i = 0; i < temp.Count; i++)
			{
				array[arrayIndex + i] = temp[i];
			}
		}

		/// <summary>
		/// Returns the count of live objects.
		/// </summary>
		/// <remarks>For a more accurate count call CleanUp before reading this property.</remarks>
		public int Count
		{
			get { return m_Collection.Count; }
		}

		/// <summary>
		/// Always returns true
		/// </summary>
		bool ICollection<T>.IsReadOnly
		{
			get { return false; }
		}

		/// <summary>
		/// Removes the indicated item from the array
		/// </summary>
		/// <param name="item"></param>
		/// <returns>Returns true if the collection previously contained the item</returns>
		/// <remarks>If an item appears more than once only the first instance will be removed.</remarks>
		public bool Remove(T item)
		{
			if (item == null)
				throw new ArgumentNullException("item", "item is null.");
			Contract.EndContractBlock();

			var result = Contains(item);
			if (result)
			{
				var newCollection = new List<WeakReference>(m_Collection.Count);
				var found = false; //used to ensure we only remove one
				foreach (var wr in m_Collection)
				{
					Contract.Assume(wr != null);

					var target = (T)wr.Target;
					if (target == null)
						continue;

					if (found || !(object.Equals(target, item)))
						newCollection.Add(wr);
					else
						found = true; //skip the first one;
				}
				m_Collection = newCollection;
			}
			return result;
		}

		/// <summary>
		/// Removes the dead references from the collection
		/// </summary>
		public void CleanUp()
		{
			m_Collection = m_Collection.Where(x => x.IsAlive).ToList();
		}

		/// <summary>
		/// Returns an enumerator containing references that were live at the time this is called.
		/// </summary>
		/// <returns></returns>
		[Pure]
		public IEnumerator<T> GetEnumerator()
		{
			return m_Collection.Select(x => (T)x.Target).Where(x => x != null).ToList().GetEnumerator();
		}

		[Pure]
		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
		[ContractInvariantMethod]
		void ObjectInvariant()
		{
			Contract.Invariant(m_Collection != null);
			//Contract.Invariant(Contract.ForAll(m_Collection, wr => wr != null));
		}


	}
}
